<!-- eslint-disable vue/attribute-hyphenation -->
<template>
  <view class="zm-list">
    <slot name="header"></slot>
    <view class="zm-list__inner">
      <scroll-view
        class="zm-list__view"
        :style="[viewStyle]"
        enable-flex
        scroll-anchoring
        enable-back-to-top
        :upper-threshold="5"
        :state="state"
        :scroll-y="scrollY"
        :change:state="wxs.onState"
        :lower-threshold="useOptions.lowerThreshold"
        :pulldownRefreshStatus="pulldownRefreshStatus"
        :change:pulldownRefreshStatus="wxs.onPulldownRefreshStatus"
        @scroll="scroll"
        @touchend="wxs.touchend"
        @touchmove="wxs.touchmove"
        @touchstart="wxs.touchstart"
        @touchcancel="wxs.touchcancel"
        @mouseup="wxs.mouseup"
        @mousedown="wxs.mousedown"
        @mousemove="wxs.mousemove"
        @mouseleave="wxs.mouseleave"
        @scrolltoupper="scrolltoupper"
        @scrolltolower="scrolltolower"
      >
        <view class="zm-list__touch">
          <view class="zm-list__main" :class="{ 'safe-area-inset-bottom': useOptions.safeArea }" :style="[mainStyle]">
            <view v-if="isShowRefresh" class="zm-list__refresh">
              <slot name="refresh">
                <view class="refresh-inner">
                  <view class="refresh-icon" :class="{ open: pulldownRefreshStatus === 'open' }" :style="[refreshIconStyle]"></view>
                  <text class="refresh-text" :style="[refreshTextStyle]">{{ refreshStatusText }}</text>
                </view>
              </slot>
            </view>
            <view v-if="isShowEmpty" class="zm-list__empty">
              <slot name="empty">
                <view class="empty-inner">
                  <image class="empty-image" :src="useOptions.emptyImage" mode="aspectFit"></image>
                  <text class="empty-text" :style="[emptyTextStyle]">{{ useOptions.emptyText }}</text>
                </view>
              </slot>
            </view>
            <view v-else class="zm-list__content">
              <slot></slot>
            </view>
            <view v-if="isShowLoad" class="zm-list__load">
              <slot name="load">
                <view class="load-inner">
                  <view v-if="loading" class="load-animation" :style="[loadIconStyle]"></view>
                  <text class="load-text" :style="[loadTextStyle]">{{ loadStatusText }}</text>
                </view>
              </slot>
            </view>
          </view>
        </view>
      </scroll-view>
    </view>
    <slot name="footer"></slot>
  </view>
</template>

<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
<script src="./wxs/index.wxs" module="wxs" lang="wxs"></script>
<!-- #endif -->
<script>
import mixins from "../mixins"
import { pick, debounce } from "../utils/utils"
import { useStyle, useUnitToPx, useRgb, useUnit } from "../utils/style"
const systemInfo = uni.getSystemInfoSync()
export default {
  name: "zm-list",
  mixins: [mixins],
  props: { options: { type: Object, default: () => ({}) } },
  data() {
    return {
      page: 1, // 当前分页
      size: 15, // 分页大小
      list: [], // 列表数据
      total: Infinity, // 列表总数
      isTop: true, // 是否滚动到顶部
      isBottom: false, // 是否滚动到底部
      isTouch: false, // 是否正在触摸
      loaded: false, // 是否已加载过
      loading: false, // 是否正在加载
      scrollY: true, // 是否允许滚动
      refreshing: false, // 是否正在刷新
      scrollTop: 0, // 距离顶部滚动位置
      pulldownHeight: 0, // 下拉高度
      pullupLoadStatus: "", // 上拉加载状态
      pulldownRefreshStatus: "", // 下拉刷新状态
      maxReloadCount: 0, // 自动重新加载次数统计
      state: {
        isTop: true,
        isBottom: false,
        toTopTimestamp: null,
        pulldownSpeed: 0.5,
        pulldownThreshold: 80,
        pulldownRefreshFinishDelay: 800,
        disablePulldownRefresh: false,
        platform: systemInfo.uniPlatform
      },
      viewRect: {},
      touchRect: {},
      useOptions: {
        page: 1, // 当前分页
        size: 15, // 分页大小
        auto: true, // 是否自动加载
        height: "100%", // 列表高度
        pageKey: "page", // 分页参数名
        sizeKey: "pageSize", // 分页大小参数名
        safeArea: false, // 是否开启安全区域适配
        pulldownSpeed: 0.5, // 下拉速度
        pulldownThreshold: 60, // 下拉阈值
        pulldownRefreshDelay: 300, // 下拉刷新延迟
        pulldownRefreshFinishDelay: 800, // 下拉刷新完成后的延迟
        pullupThreshold: 40, // 距离底部上拉加载距离
        disablePullupLoad: false, // 是否禁用上拉加载
        disablePulldownRefresh: false, // 是否禁用下拉刷新
        emptyTextColor: "#82848a", // 空提示文字颜色
        loadTextColor: "#82848a", // 上拉加载文字颜色
        loadIconColor: "#82848a", // 上拉加载图标颜色
        refresherTextColor: "#82848a", // 下拉刷新文字颜色
        refresherIconColor: "#82848a", // 下拉刷新图标颜色
        emptyText: "暂无内容~", // 空数据提示文字
        pulldownText: "下拉刷新~", // 下拉中的文字
        pulldownFinishText: "松开刷新~", // 下拉完成的文字
        loadText: "正在加载中~", // 加载中文字
        loadFailText: "加载失败啦~", // 加载失败文字
        loadFinishText: "没有更多啦~", // 加载全部完成
        refreshingText: "正在刷新~", // 正在刷新文字
        refreshFailText: "刷新失败~", // 刷新失败文字
        refreshSuccessText: "刷新成功~" // 刷新成功文字
      }
    }
  },
  computed: {
    // 是否显示下拉刷新
    isShowRefresh() {
      return this.useOptions.disablePulldownRefresh == false
    },
    // 是否显示空数据
    isShowEmpty() {
      return this.total == 0
    },
    // 是否显示下拉加载
    isShowLoad() {
      if (this.refreshing) return false
      if (this.isShowEmpty) return false
      return this.useOptions.disablePullupLoad == false
    },
    viewStyle() {
      const style = {}
      if (this.useOptions.height === "auto") style.position = "static"
      else style.height = useUnit(this.useOptions.height)
      return useStyle(style)
    },
    mainStyle() {
      const style = {}
      if (this.isShowEmpty) style.height = "100%"
      return useStyle(style)
    },
    // 加载状态文字
    loadStatusText() {
      switch (this.pullupLoadStatus) {
        case "load":
          return this.useOptions.loadText
        case "fail":
          return this.useOptions.loadFailText
        case "finish":
          return this.useOptions.loadFinishText
        default:
          return ""
      }
    },
    // 加载状态图标样式
    loadIconStyle() {
      const style = {}
      const { loadIconColor } = this.useOptions
      const { r, g, b } = useRgb(loadIconColor)
      style.borderColor = `rgba(${r},${g},${b},0.2)`
      style.borderTopColor = loadIconColor
      return useStyle(style)
    },
    // 加载状态文字样式
    loadTextStyle() {
      return useStyle({ color: this.useOptions.loadTextColor })
    },
    // 刷新状态文字
    refreshStatusText() {
      switch (this.pulldownRefreshStatus) {
        case "open":
          return this.useOptions.refreshingText
        case "fail":
          return this.useOptions.refreshFailText
        case "wait":
          return this.useOptions.pulldownFinishText
        case "success":
          return this.useOptions.refreshSuccessText
        default:
          return this.useOptions.pulldownText
      }
    },
    // 刷新状态图标样式
    refreshIconStyle() {
      const style = {}
      const { pulldownHeight, pulldownRefreshStatus } = this
      const { refresherIconColor, pulldownThreshold } = this.useOptions
      const { r, g, b } = useRgb(refresherIconColor)
      const rate = pulldownHeight / pulldownThreshold
      style.borderColor = `rgba(${r},${g},${b},0.2)`
      style.borderTopColor = refresherIconColor
      if (pulldownRefreshStatus !== "open") {
        style.transform = `rotate(${360 * rate}deg)`
      }
      return useStyle(style)
    },
    // 刷新状态文字样式
    refreshTextStyle() {
      const style = {}
      style.color = this.useOptions.refresherTextColor
      return useStyle(style)
    },
    // 空数据提示文字样式
    emptyTextStyle() {
      return useStyle({ color: this.useOptions.emptyTextColor })
    }
  },
  watch: {
    options: {
      handler(options) {
        this.initOptions(options)
      },
      deep: true,
      immediate: true
    }
  },
  async mounted() {
    await this.resize()
    if (this.useOptions.auto) this.load()
  },
  methods: {
    initOptions(options) {
      this.useOptions.emptyImage = this.useImage("/zuimei-film-oss/1.0.0/empty/zwsj-empty.png")
      this.useOptions = Object.assign(this.useOptions, options)
      this.page = this.useOptions.page
      this.size = this.useOptions.size
      this.state = Object.assign(this.state, pick(this.useOptions, ["pulldownSpeed", "pulldownThreshold", "pulldownRefreshFinishDelay", "disablePulldownRefresh"]))
    },
    async resize(d = false) {
      const handle = async () => {
        this.viewRect = await this.useRect(".zm-list__view")
        this.touchRect = await this.useRect(".zm-list__touch")
      }
      if (d) debounce(handle, 150, true)
      else await handle()
    },
    async auto() {
      await this.resize()
      const height = this.viewRect.height * 2
      if (this.maxReloadCount >= 5) throw new Error("自动重新加载次数超过5次，请检查网络或数据源是否正确")
      if (this.touchRect.height < height) this.load()
    },
    query(options = {}) {
      const { type = "refresh" } = options
      if (type === "load") this.load(options)
      if (type === "refresh") this.refresh(options)
    },
    load(options = {}) {
      if (this.loading) return
      if (this.list.length >= this.total) return
      if (this.useOptions.disablePullupLoad) return
      this.loading = true
      this.pullupLoadStatus = "load"
      this.$emit("load", { [this.useOptions.sizeKey]: this.size, [this.useOptions.pageKey]: this.page, type: "load", fail: this.loadFail, success: this.loadSuccess })
      this.$emit("query", { [this.useOptions.sizeKey]: this.size, [this.useOptions.pageKey]: this.page, type: "load", fail: this.loadFail, success: this.loadSuccess })
    },
    loadFail() {
      this.loaded = true
      this.loading = false
      this.pullupLoadStatus = "fail"
    },
    loadSuccess({ list, total }) {
      this.maxReloadCount = this.list.length == list.length ? this.maxReloadCount + 1 : 0
      this.page++
      this.list = [...this.list, ...list]
      this.total = total
      this.loaded = true
      this.loading = false
      this.pullupLoadStatus = this.list.length >= this.total ? "finish" : ""
      this.auto()
    },
    refresh(options = {}) {
      if (this.refreshing) return
      if (this.useOptions.disablePulldownRefresh) return
      this.page = this.useOptions.page
      this.loading = true
      this.refreshing = true
      this.maxReloadCount = 0
      this.pullupLoadStatus = "load"
      this.$emit("query", { [this.useOptions.sizeKey]: this.size, [this.useOptions.pageKey]: this.page, type: "refresh", fail: this.refreshFail, success: this.refreshSuccess })
      this.$emit("refresh", { [this.useOptions.sizeKey]: this.size, [this.useOptions.pageKey]: this.page, type: "refresh", fail: this.refreshFail, success: this.refreshSuccess })
    },
    refreshFail() {
      this.loaded = true
      this.refreshing = false
      this.pulldownRefreshStatus = "fail"
    },
    refreshSuccess({ list, total }) {
      this.list = list
      this.total = total
      this.loaded = true
      this.loading = false
      this.refreshing = false
      this.pullupLoadStatus = ""
      this.pulldownRefreshStatus = "success"
      if (this.list.length >= this.total) this.pullupLoadStatus = "finish"
      this.auto()
    },
    dragPulldownRefresh(state) {
      this.scrollY = false
      this.pulldownHeight = state.pulldownHeight
      this.pulldownRefreshStatus = state.pulldownRefreshStatus
    },
    // 开启下拉刷新
    openPulldownRefresh(state) {
      setTimeout(() => {
        this.refresh()
      }, this.useOptions.pulldownRefreshDelay)
      this.pulldownRefreshStatus = state.pulldownRefreshStatus
    },
    // 取消下拉刷新
    cancelPulldownRefresh(state) {
      this.scrollY = true
    },
    // 失败下拉刷新
    failPulldownRefresh(state) {
      this.scrollY = true
    },
    // 成功下拉刷新
    successPulldownRefresh(state) {
      this.scrollY = true
    },
    // 视图滚动
    scroll({ detail }) {
      this.resize(true)
      this.$emit("scroll", this.scrollTop)

      this.isTop = detail.scrollTop <= 5
      this.isBottom = detail.scrollTop + this.viewRect.height + useUnitToPx(this.useOptions.pullupThreshold) >= this.touchRect.height
      this.scrollTop = detail.scrollTop
    },
    // 滚动到顶部
    scrolltoupper() {
      this.isTop = true
      this.state.isTop = true
      this.state.toTopTimestamp = Date.now()
      this.$emit("scrolltoupper", this.scrollTop)
    },
    // 滚动到底部
    scrolltolower() {
      this.isBottom = true
      this.state.isBottom = true
      this.load()
      this.$emit("scrolltolower", this.scrollTop)
    },
    touchstart() {
      this.resize(true)
      this.isTouch = true
    },
    touchmove(event) {},
    touchend() {
      this.scrollY = true
      this.isTouch = false
    },
    touchcancel() {
      this.isTouch = false
    }
  }
}
</script>

<style lang="scss">
.zm-list {
  flex: 1;
  width: 100%;
  height: 100%;
  display: flex;
  position: relative;
  flex-direction: column;
  &__inner {
    top: 0;
    left: 0;
    flex: 1;
    width: 100%;
    height: 100%;
    display: flex;
    overflow: hidden;
    position: absolute;
    flex-direction: row;
  }
  &__view {
    top: 0;
    left: 0;
    right: 0;
    height: 100%;
    display: flex;
    position: absolute;
    overflow-anchor: none;
  }
  &__touch {
    width: 100%;
    height: 100%;
    display: flex;
    position: relative;
    flex-direction: column;
  }
  &__main {
    width: 100%;
    position: relative;
  }
  &__refresh {
    left: 0;
    width: 100%;
    display: flex;

    position: absolute;
    align-items: center;
    justify-content: center;
    transform: translateY(-100%);
    .refresh-inner {
      display: flex;
      padding: 15px 0;
      align-items: center;
      justify-content: center;
      .refresh-icon {
        width: 32rpx;
        height: 32rpx;
        border-width: 4rpx;
        border-style: solid;
        border-radius: 50%;
        &.open {
          animation: spin 0.5s linear infinite;
        }
        @keyframes spin {
          to {
            transform: rotate(360deg);
          }
        }
      }
      .refresh-text {
        margin-left: 10rpx;
      }
    }
  }
  &__empty {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    position: absolute;
    align-items: center;
    flex-direction: column;
    .empty-inner {
      margin: auto;
      display: flex;
      align-items: center;
      padding-bottom: 30%;
      flex-direction: column;
      .empty-image {
        width: 580rpx;
        height: 310rpx;
      }
      .empty-text {
        font-size: 28rpx;
        color: #606266;
        margin-top: 20rpx;
      }
    }
  }
  &__content {
    display: flex;
    flex-direction: column;
  }
  &__load {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    .load-inner {
      padding: 15px 0;
      display: flex;
      align-items: center;
      justify-content: center;
      .load-animation {
        width: 32rpx;
        height: 32rpx;
        border-width: 4rpx;
        border-style: solid;
        border-radius: 50%;
        animation: spin 0.5s linear infinite;
      }
      .load-text {
        margin-left: 10rpx;
      }
    }
  }
}
</style>
